import os
import re
import shutil
from pathlib import Path
from typing import Dict, Optional

from core.cloners_module.base_cloner import BaseProjectCloner
from core.utils_module.language_utils import collect_extensions, get_extension_group


class AndroidProjectCloner(BaseProjectCloner):
    """负责 Android 项目的克隆逻辑"""

    def clone(
        self,
        source_path: str,
        target_path: str,
        project_info: Dict,
        new_package_name: Optional[str],
        new_project_name: Optional[str],
        modify_fingerprints: bool,
    ) -> str:
        """克隆 Android 项目
        Args:
            source_path (str): 源项目路径
            target_path (str): 目标项目路径
            project_info (Dict): 项目结构信息
            new_package_name (Optional[str]): 新的包名，如果为 None 则自动生成
            new_project_name (Optional[str]): 新的项目名，如果为 None 则自动生成
            modify_fingerprints (bool): 是否修改文件指纹
        Returns:
            str: 目标项目路径
        """
        self.logger.log_operation("克隆Android项目", "开始复制文件")

        # 尝试自动识别原始包名和项目名
        original_package_name = self._get_android_package_name(project_info)
        original_project_name = self._get_android_project_name(source_path, project_info)

        # 自动生成包名和项目名
        if not original_package_name:
            self.logger.log_warning("未能自动识别原始包名，将使用默认包名 com.example.app")
            original_package_name = "com.example.app"

        if not new_package_name:
            new_package_name = f"{original_package_name}.clone"

        if not new_project_name:
            new_project_name = f"{original_project_name}Clone"

        # 复制项目文件
        self._copy_project_files(source_path, target_path, "android")

        # 清理构建产物和缓存（在修改前清理）
        self._clean_android_build_artifacts(target_path)

        # 修改包名和项目名
        if new_package_name != original_package_name:
            self._update_android_package_name(target_path, original_package_name, new_package_name)

        if new_project_name != original_project_name:
            self._update_android_project_name(target_path, original_project_name, new_project_name)

        # 重组包结构
        self._reorganize_android_package_structure(target_path, original_package_name, new_package_name)
        self._update_android_project_configs(target_path, project_info, original_package_name, new_package_name)

        # 更新应用名称和其他资源
        self._update_android_app_name(target_path, original_project_name, new_project_name)

        # 根据需要修改文件指纹
        self._modify_fingerprints_if_needed(target_path, "android", modify_fingerprints)

        self.logger.log_operation(
            "Android项目克隆完成",
            f"新包名: {new_package_name}, 新项目名: {new_project_name}",
        )
        return target_path

    def _get_android_package_name(self, project_info: Dict) -> Optional[str]:
        """
        尝试从 AndroidManifest.xml 或 build.gradle 文件中提取包名
        优先级：AndroidManifest.xml package > build.gradle namespace > build.gradle applicationId
        """
        # 1. 尝试从 AndroidManifest.xml 获取（旧版Android项目）
        for manifest_path in project_info["structure"].get("manifest", []):
            # 跳过 build 目录中的合并后的 manifest
            if "/build/" in manifest_path.replace("\\", "/"):
                continue
            try:
                with open(manifest_path, "r", encoding="utf-8") as handle:
                    content = handle.read()
                match = re.search(r'package\s*=\s*"([^"]+)"', content)
                if match:
                    package_name = match.group(1).strip()
                    self.logger.log_operation("检测到包名", f"从 AndroidManifest.xml: {package_name}")
                    return package_name
            except Exception:  # noqa: BLE001
                continue

        # 2. 尝试从 build.gradle 获取 namespace（现代Android项目优先）
        for gradle_path in project_info["structure"].get("build_gradle", []):
            # 优先查找 app/build.gradle
            if "app" not in gradle_path.replace("\\", "/"):
                continue
            try:
                with open(gradle_path, "r", encoding="utf-8") as handle:
                    content = handle.read()

                # 查找 namespace（AGP 7.0+）
                namespace_match = re.search(
                    r'''namespace\s+['"](.+?)['"]''',
                    content
                )
                if namespace_match:
                    package_name = namespace_match.group(1).strip()
                    self.logger.log_operation("检测到包名", f"从 build.gradle namespace: {package_name}")
                    return package_name

                # 查找 applicationId（备用）
                app_id_match = re.search(
                    r'''applicationId\s+['"](.+?)['"]''',
                    content
                )
                if app_id_match:
                    package_name = app_id_match.group(1).strip()
                    self.logger.log_operation("检测到包名", f"从 build.gradle applicationId: {package_name}")
                    return package_name
            except Exception:  # noqa: BLE001
                continue

        # 3. 如果 app/build.gradle 没找到，尝试所有 build.gradle
        for gradle_path in project_info["structure"].get("build_gradle", []):
            try:
                with open(gradle_path, "r", encoding="utf-8") as handle:
                    content = handle.read()

                # 查找 namespace
                namespace_match = re.search(
                    r'''namespace\s+['"](.+?)['"]''',
                    content
                )
                if namespace_match:
                    package_name = namespace_match.group(1).strip()
                    self.logger.log_operation("检测到包名", f"从 {gradle_path} namespace: {package_name}")
                    return package_name

                # 查找 applicationId
                app_id_match = re.search(
                    r'''applicationId\s+['"](.+?)['"]''',
                    content
                )
                if app_id_match:
                    package_name = app_id_match.group(1).strip()
                    self.logger.log_operation("检测到包名", f"从 {gradle_path} applicationId: {package_name}")
                    return package_name
            except Exception:  # noqa: BLE001
                continue

        return None

    def _get_android_project_name(self, project_path: str, project_info: Dict) -> str:
        """
        尝试从 settings.gradle 或 settings.gradle.kts 文件中提取项目名称
        """
        settings_gradle = project_info["structure"].get("settings_gradle")
        if settings_gradle:
            try:
                with open(settings_gradle, "r", encoding="utf-8") as handle:
                    content = handle.read()
                match = re.search(r"rootProject\.name\s*=\s*['\"]([^'\"]+)['\"]", content)
                if match:
                    return match.group(1)
            except Exception:  # noqa: BLE001
                pass
        return Path(project_path).name

    def _update_android_package_name(self, project_path: str, old_package: str, new_package: str) -> None:
        """
        更新项目中的包名引用
        包括 Java/Kotlin 源文件中的 package 和 import 语句，以及 XML 布局文件中的包名
        """

        # 收集相关文件扩展名
        code_extensions = collect_extensions(["java", "kotlin"])
        gradle_extensions = {".gradle", ".gradle.kts"}
        xml_extensions = set(get_extension_group("xml_like"))
        android_file_extensions = code_extensions.union(gradle_extensions).union(xml_extensions)

        # 遍历所有相关文件，更新包名引用
        for root, dirs, files in os.walk(project_path):
            for file in files:
                lower = file.lower()
                if not any(lower.endswith(ext) for ext in android_file_extensions):
                    continue

                file_path = os.path.join(root, file)
                try:
                    with open(file_path, "r", encoding="utf-8") as handle:
                        content = handle.read()
                    original_content = content

                    if file.endswith((".java", ".kt")):
                        lines = content.splitlines(keepends=True)
                        changed_lines = False

                        def _replace_identifier(identifier: str) -> Optional[str]:
                            """
                            替换包名标识符
                            只替换完全匹配或以旧包名加点开头的标识符
                            """
                            if identifier == old_package:
                                return new_package
                            prefix = f"{old_package}."
                            if identifier.startswith(prefix):
                                return f"{new_package}{identifier[len(old_package):]}"
                            return None

                        # 逐行处理，替换 package 和 import 语句
                        for index, line in enumerate(lines):
                            package_match = re.match(r"(\s*package\s+)([^\s;]+)(.*)", line)
                            if package_match:
                                identifier = package_match.group(2)
                                replacement = _replace_identifier(identifier)
                                if replacement:
                                    new_line = f"{package_match.group(1)}{replacement}{package_match.group(3)}"
                                    if new_line != line:
                                        lines[index] = new_line
                                        changed_lines = True
                                continue

                            import_match = re.match(r"(\s*import\s+(?:static\s+)?)([^\s;]+)(.*)", line)
                            if import_match:
                                identifier = import_match.group(2)
                                replacement = _replace_identifier(identifier)
                                if replacement:
                                    new_line = f"{import_match.group(1)}{replacement}{import_match.group(3)}"
                                    if new_line != line:
                                        lines[index] = new_line
                                        changed_lines = True

                        if changed_lines:
                            content = "".join(lines)
                   
                    elif file.endswith(".xml"):  # 更新 XML 文件中的包名
                        content = re.sub(
                            rf'package\s*=\s*"{re.escape(old_package)}"',
                            f'package="{new_package}"',
                            content,
                        )
                        content = re.sub(
                            rf'{re.escape(old_package)}(\.[\w.]+)',
                            f"{new_package}\\1",
                            content,
                        )

                    elif file.endswith((".gradle", ".gradle.kts")): # 更新 Gradle 文件中的包名
                        def _replace_gradle_keyword(text: str, keyword: str) -> str:
                            """
                            替换 Gradle 文件中的包名
                            """
                            pattern = re.compile(
                                rf"({keyword}\s*(?:=\s*)?)(['\"])([^'\"]+)\2"
                            )

                            def _replacer(match: re.Match) -> str:
                                """
                                 替换 Gradle 文件中的包名
                                 """
                                prefix, quote, value = match.groups()
                                if value == old_package:
                                    new_value = new_package
                                elif value.startswith(f"{old_package}."):
                                    new_value = f"{new_package}{value[len(old_package):]}"
                                else:
                                    return match.group(0)
                                return f"{prefix}{quote}{new_value}{quote}"

                            return pattern.sub(_replacer, text)

                        before_gradle = content
                        for keyword in ("applicationId", "namespace", "testApplicationId", "testNamespace"):
                            content = _replace_gradle_keyword(content, keyword)
                        if content != before_gradle:
                            self.logger.log_operation("更新Gradle标识符", file_path)
                    if content != original_content:
                        with open(file_path, "w", encoding="utf-8") as handle:
                            handle.write(content)
                        self.logger.log_file_processing(file_path, f"更新包名: {old_package} -> {new_package}")
                except Exception as exc:  # noqa: BLE001
                    self.logger.log_warning(f"更新文件失败 {file_path}: {str(exc)}")

    def _update_android_project_name(self, project_path: str, old_name: str, new_name: str) -> None:
        """
        更新 settings.gradle 或 settings.gradle.kts 中的 rootProject.name
        """
        for settings_filename in ("settings.gradle", "settings.gradle.kts"):
            settings_file = os.path.join(project_path, settings_filename)
            if not os.path.exists(settings_file):
                continue
            try:
                with open(settings_file, "r", encoding="utf-8") as handle:
                    content = handle.read()

                replacement = (
                    f'rootProject.name = "{new_name}"'
                    if settings_filename.endswith(".kts")
                    else f"rootProject.name = '{new_name}'"
                )
                updated = re.sub(
                    r"rootProject\.name\s*=\s*['\"]([^'\"]+)['\"]",
                    replacement,
                    content,
                )
                if updated != content:
                    with open(settings_file, "w", encoding="utf-8") as handle:
                        handle.write(updated)
                    self.logger.log_file_processing(settings_file, f"更新rootProject名称 -> {new_name}")
            except Exception as exc:  # noqa: BLE001
                self.logger.log_warning(f"更新{settings_filename}失败: {str(exc)}")

    def _reorganize_android_package_structure(self, project_path: str, old_package: str, new_package: str) -> None:
        """
        重组 Android 项目的包结构
        将源代码文件从旧包路径移动到新包路径

        例如：
        - 旧包名：com.example.testapp
        - 新包名：com.test.copy.www
        - 将 src/main/java/com/example/testapp/* 移动到 src/main/java/com/test/copy/www/*
        """
        if old_package == new_package:
            return

        old_path_parts = old_package.split(".")
        new_path_parts = new_package.split(".")

        # 查找所有源代码根目录（java 或 kotlin 目录）
        source_roots = []
        for root, dirs, _ in os.walk(project_path):
            # 跳过 build 目录
            dirs[:] = [d for d in dirs if d not in ["build", ".gradle", ".idea"]]

            # 只处理 src/main/java 或 src/main/kotlin 这样的源码根目录
            # 判断条件：目录名是 java 或 kotlin，且父目录包含 src
            if os.path.basename(root) in ["java", "kotlin"] and "src" in root:
                source_roots.append(root)

        self.logger.log_operation("查找源代码根目录", f"找到 {len(source_roots)} 个源码目录")

        # 在每个源代码根目录下重组包结构
        for source_root in source_roots:
            old_package_path = os.path.join(source_root, *old_path_parts)

            if not os.path.exists(old_package_path):
                self.logger.log_operation(
                    "跳过源码目录",
                    f"{source_root}: 未找到旧包路径 {'/'.join(old_path_parts)}"
                )
                continue

            if not os.path.isdir(old_package_path):
                self.logger.log_warning(f"旧包路径不是目录，跳过: {old_package_path}")
                continue

            new_package_path = os.path.join(source_root, *new_path_parts)

            if old_package_path == new_package_path:
                self.logger.log_operation("包路径相同", "跳过重组")
                continue

            try:
                # 创建新包的父目录
                new_package_parent = os.path.dirname(new_package_path)
                if new_package_parent:
                    os.makedirs(new_package_parent, exist_ok=True)

                # 如果目标路径已存在，需要合并
                if os.path.exists(new_package_path):
                    if not os.path.isdir(new_package_path):
                        self.logger.log_error(f"目标包路径存在但不是目录: {new_package_path}")
                        continue

                    self.logger.log_operation("合并包目录", f"{old_package_path} -> {new_package_path}")

                    # 移动所有文件和子目录到新位置
                    moved_count = 0
                    for entry in os.listdir(old_package_path):
                        src_entry = os.path.join(old_package_path, entry)
                        dst_entry = os.path.join(new_package_path, entry)

                        if os.path.exists(dst_entry):
                            self.logger.log_warning(f"目标已存在，跳过: {entry}")
                            continue

                        shutil.move(src_entry, dst_entry)
                        moved_count += 1

                    self.logger.log_operation("合并完成", f"移动了 {moved_count} 个文件/目录")

                    # 清理空的旧包目录
                    if not os.listdir(old_package_path):
                        shutil.rmtree(old_package_path)
                        self.logger.log_operation("删除空目录", old_package_path)
                else:
                    # 直接移动整个目录
                    self.logger.log_operation(
                        "重组包结构",
                        f"{'/'.join(old_path_parts)} -> {'/'.join(new_path_parts)}"
                    )
                    shutil.move(old_package_path, new_package_path)

                # 清理旧包的空父目录（从最深的开始）
                self._clean_empty_package_dirs(source_root, old_path_parts)

            except Exception as exc:  # noqa: BLE001
                self.logger.log_error(f"重组包结构失败 {old_package_path}: {exc}")

    def _clean_empty_package_dirs(self, source_root: str, package_parts: list) -> None:
        """
        清理空的包目录（从最深层开始）
        例如：如果 com/example/testapp 为空，则删除 testapp, example, com（如果它们都为空）
        """
        for i in range(len(package_parts), 0, -1):
            dir_to_check = os.path.join(source_root, *package_parts[:i])
            if os.path.exists(dir_to_check) and os.path.isdir(dir_to_check):
                if not os.listdir(dir_to_check):
                    try:
                        shutil.rmtree(dir_to_check)
                        self.logger.log_operation("清理空目录", dir_to_check)
                    except Exception as exc:  # noqa: BLE001
                        self.logger.log_warning(f"删除空目录失败 {dir_to_check}: {exc}")
                        break
                else:
                    # 如果目录不为空，停止向上清理
                    break

    def _update_android_project_configs(
        self,
        project_path: str,
        project_info: Dict,
        old_package: str,
        new_package: str,
    ) -> None:
        """
        更新Android项目配置
        包括 build.gradle 中的 applicationId 和 namespace，以及 settings.gradle 中的项目引用
        """
        build_files = project_info["structure"].get("build_gradle", [])
        settings_gradle = project_info["structure"].get("settings_gradle")
        source_root = project_info.get("root")

        # 循环遍历更新 build.gradle 文件中的 applicationId 和 namespace
        for build_file in build_files:
            target_build_file = self._resolve_target_path(project_path, source_root, build_file)
            if not target_build_file:
                continue
            try:
                with open(target_build_file, "r", encoding="utf-8") as handle:
                    content = handle.read()

                def _replace_gradle_value(text: str, keyword: str) -> str:
                    pattern = re.compile(
                        rf"({keyword}\s*(?:=\s*)?)(['\"])([^'\"]+)\2"
                    )

                    def _replacer(match: re.Match) -> str:
                        prefix, quote, value = match.groups()
                        if value == old_package:
                            new_value = new_package
                        elif value.startswith(f"{old_package}."):
                            new_value = f"{new_package}{value[len(old_package):]}"
                        else:
                            return match.group(0)
                        return f"{prefix}{quote}{new_value}{quote}"

                    return pattern.sub(_replacer, text)

                updated = content
                for keyword in ("applicationId", "namespace", "testApplicationId", "testNamespace"):
                    updated = _replace_gradle_value(updated, keyword)
                if updated != content:
                    with open(target_build_file, "w", encoding="utf-8") as handle:
                        handle.write(updated)
                    self.logger.log_file_processing(target_build_file, f"更新Android包配置 -> {new_package}")
            except Exception as exc:  # noqa: BLE001
                self.logger.log_warning(f"更新Gradle配置失败 {target_build_file}: {exc}")

        if settings_gradle:
            target_settings = self._resolve_target_path(project_path, source_root, settings_gradle)
            if not target_settings:
                return
            try:
                with open(target_settings, "r", encoding="utf-8") as handle:
                    content = handle.read()
                updated = re.sub(
                    r"(include\s+[^\n]+)",
                    lambda match: match.group(1).replace(
                        old_package.replace(".", "-"), new_package.replace(".", "-")
                    ),
                    content,
                )
                if updated != content:
                    with open(target_settings, "w", encoding="utf-8") as handle:
                        handle.write(updated)
                    self.logger.log_file_processing(target_settings, "更新settings.gradle引用")
            except Exception as exc:  # noqa: BLE001
                self.logger.log_warning(f"更新settings.gradle失败 {target_settings}: {exc}")

    def _resolve_target_path(
        self,
        target_root: str,
        source_root: Optional[str],
        source_path: Optional[str],
    ) -> Optional[str]:
        """
        根据源文件路径和源根目录，解析目标文件路径
        如果源文件在源根目录下，则保持相对路径不变
        否则尝试直接映射到目标根目录
        """
        if not source_path:
            return None
        if source_root:
            try:
                source_root_abs = os.path.abspath(source_root)
                source_path_abs = os.path.abspath(source_path)
                if os.path.commonpath([source_root_abs, source_path_abs]) == source_root_abs:
                    relative = os.path.relpath(source_path_abs, source_root_abs)
                    candidate = os.path.join(target_root, relative)
                    return candidate if os.path.exists(candidate) else None
            except Exception:  # noqa: BLE001
                pass
        # 兜底直接映射到目标根目录
        filename = os.path.basename(source_path)
        candidate = os.path.join(target_root, filename)
        return candidate if os.path.exists(candidate) else None

    def _clean_android_build_artifacts(self, project_path: str) -> None:
        """
        清理 Android 项目的构建产物和缓存

        清理内容：
        1. build/ - 所有构建输出
        2. .gradle/ - Gradle 守护进程缓存
        3. .idea/ - Android Studio 项目配置
        4. *.iml - IntelliJ IDEA 模块文件
        5. local.properties - 本地 SDK 路径配置
        6. captures/ - 性能分析数据
        7. .externalNativeBuild/ - Native 构建缓存
        """
        self.logger.log_operation("清理构建产物", "开始清理 Android 项目缓存和构建文件")

        # 需要删除的目录列表
        dirs_to_clean = [
            "build",
            ".gradle",
            ".idea",
            "captures",
            ".externalNativeBuild",
            "app/build",
            ".cxx"  # CMake 构建缓存
        ]

        # 需要删除的文件模式
        files_to_clean = [
            "local.properties",
            "*.iml",
            "*.hprof",  # 内存快照
            ".DS_Store"  # macOS 文件
        ]

        cleaned_count = 0

        # 清理目录
        for dir_name in dirs_to_clean:
            dir_path = os.path.join(project_path, dir_name)
            if os.path.exists(dir_path):
                try:
                    shutil.rmtree(dir_path)
                    self.logger.log_operation("删除目录", dir_path)
                    cleaned_count += 1
                except Exception as exc:  # noqa: BLE001
                    self.logger.log_warning(f"删除目录失败 {dir_path}: {exc}")

        # 清理文件（包括递归查找）
        for root, dirs, files in os.walk(project_path):
            # 跳过某些目录以提高性能
            dirs[:] = [d for d in dirs if d not in ["node_modules", ".git"]]

            for file in files:
                file_path = os.path.join(root, file)
                should_delete = False

                # 检查是否匹配删除模式
                for pattern in files_to_clean:
                    if pattern.startswith("*."):
                        ext = pattern[1:]  # 例如 ".iml"
                        if file.endswith(ext):
                            should_delete = True
                            break
                    elif file == pattern:
                        should_delete = True
                        break

                if should_delete:
                    try:
                        os.remove(file_path)
                        self.logger.log_operation("删除文件", file_path)
                        cleaned_count += 1
                    except Exception as exc:  # noqa: BLE001
                        self.logger.log_warning(f"删除文件失败 {file_path}: {exc}")

        self.logger.log_operation("清理完成", f"共清理 {cleaned_count} 个文件/目录")

    def _update_android_app_name(
        self,
        project_path: str,
        old_name: str,
        new_name: str
    ) -> None:
        """
        更新 Android 应用名称

        更新位置：
        1. res/values/strings.xml 中的 app_name
        2. AndroidManifest.xml 中的 android:label (如果存在)
        """
        if old_name == new_name:
            return

        self.logger.log_operation("更新应用名称", f"{old_name} -> {new_name}")

        # 1. 更新 strings.xml
        for root, _, files in os.walk(project_path):
            for file in files:
                if file == "strings.xml" and "res/values" in root:
                    strings_path = os.path.join(root, file)
                    try:
                        with open(strings_path, "r", encoding="utf-8") as f:
                            content = f.read()

                        # 更新 app_name
                        original_content = content
                        content = re.sub(
                            r'(<string\s+name="app_name">)(.+?)(</string>)',
                            rf'\1{new_name}\3',
                            content
                        )

                        if content != original_content:
                            with open(strings_path, "w", encoding="utf-8") as f:
                                f.write(content)
                            self.logger.log_file_processing(
                                strings_path,
                                f"更新 app_name -> {new_name}"
                            )
                    except Exception as exc:  # noqa: BLE001
                        self.logger.log_warning(f"更新 strings.xml 失败 {strings_path}: {exc}")

        # 2. 更新 AndroidManifest.xml 中的 android:label (可选)
        for root, _, files in os.walk(project_path):
            for file in files:
                if file == "AndroidManifest.xml" and "/build/" not in root:
                    manifest_path = os.path.join(root, file)
                    try:
                        with open(manifest_path, "r", encoding="utf-8") as f:
                            content = f.read()

                        original_content = content
                        # 如果直接使用了字符串（不推荐），也更新它
                        content = re.sub(
                            rf'(android:label\s*=\s*["\']){re.escape(old_name)}(["\'])',
                            rf'\1{new_name}\2',
                            content
                        )

                        if content != original_content:
                            with open(manifest_path, "w", encoding="utf-8") as f:
                                f.write(content)
                            self.logger.log_file_processing(
                                manifest_path,
                                f"更新 android:label -> {new_name}"
                            )
                    except Exception as exc:  # noqa: BLE001
                        self.logger.log_warning(f"更新 AndroidManifest.xml 失败 {manifest_path}: {exc}")
